package logger.sd.server;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import logger.sd.protocol.LRP;
import logger.sd.server.service.Replicator;
import logger.sd.server.service.SimpleReplicator;
import logger.sd.server.service.ServiceFactory;
import logger.sd.server.service.Service;
import logger.sd.server.service.TokenArriveService;
import logger.sd.server.service.TokenSender;
import logger.sd.server.service.TokenSenderService;

/**
 * Conexao Trata cada requisicao do cliente ao servidor
 * 
 * @author Wendell
 * 
 */
public class Connection implements Runnable, Observer {

	private TokenSender tokenSender;
	private Replicator replicator;
	private Socket clientSocket;
	private LoggingServer server;

	private BufferedReader in;
	private DataOutputStream out;

	private List<Service> requestBuffer;
	private List<Integer> replyBuffer;

	private static String endl = System.getProperty("line.separator");

	/**
	 * Construtor Recebe o socket do cliente
	 * 
	 * @param cliente
	 *            Socket do cliente
	 */
	public Connection(Socket cliente, LoggingServer server) {
		this.server = server;
		this.clientSocket = cliente;
		replicator = new SimpleReplicator(server);
		requestBuffer = new ArrayList<Service>();
		replyBuffer = new ArrayList<Integer>();
		try {
			in = new BufferedReader(new InputStreamReader(
					clientSocket.getInputStream()));
			out = new DataOutputStream(clientSocket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Executa o servico implementado
	 */
	public void run() {

		String requisicao = new String();
		Service service;

		tokenSender = new TokenSenderService(server); // config file
		tokenSender.schedule();

		while (!clientSocket.isClosed() && requisicao != null) {

			try {
				requisicao = in.readLine();

				if (requisicao == null) {
					break;
				}

				String[] line = requisicao.split(":");

				String code = new String();
				String message = new String();

				boolean isReplicate;
				if (line.length < 2) {
					send(LRP.error01());
					break;
				} else if (line[0].equals(LRP.END.getMessage())) {
					send(LRP.okMessage());
					break;
				} else if (line[0].equals(LRP.REPLY.getMessage())) {
					code = line[1];
					message = line[2];
					isReplicate = true;
				} else {
					code = line[0];
					message = line[1];
					isReplicate = false;
				}

				service = ServiceFactory.createService(code, message,
						clientSocket.getInetAddress().getHostAddress(), server);

				boolean isTokenMessage = service.getClass().getName()
						.equals(TokenArriveService.class.getName());

				if (server.hasToken() || isTokenMessage) {
					// Se nao for uma mensagem replicada
					if (!isReplicate) {
						replicator.insertBuffer(service.getCode(),
								service.getRequest());
					}
					send(service.trataRequisicao());
				} else {
					insertBuffer(service, isReplicate);
				}

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 
	 * @param isReplicate
	 * @param serverMessageId
	 * @throws IOException
	 */
	private void insertBuffer(Service service, boolean isReplicate)
			throws IOException {

		System.out.println("[buffer]: " + service.getRequest());

		Integer rep = 0;
		if (isReplicate) {
			rep = 1;
		}
		send(LRP.bufferMessage());
		server.addListener(this);

		this.requestBuffer.add(service);
		this.replyBuffer.add(rep);
	}

	/**
	 * Envia uma mensagem ao cliente
	 * 
	 * @param message
	 *            Mensagem que serah enviada ao cliente
	 * @throws IOException
	 */
	private void send(String message) throws IOException {
		out.writeBytes(message + endl);
	}

	/**
	 * Inicia o servico implementado. Difere do run(), que esta implementando
	 * Runnable
	 */
	public void start() {
		this.run();
	}

	/**
	 * Funcao que faz alguma coisa quando o token chega...
	 */
	@Override
	public synchronized void update(Observable o, Object arg) {
		for (int i = 0; i < this.requestBuffer.size(); ++i) {
			Service s = this.requestBuffer.get(i);
			if (replyBuffer.get(i) == 0) {
				replicator.insertBuffer(s.getCode(), s.getRequest());
			}
			s.trataRequisicao();
		}
		o.deleteObserver(this);
	}

	/**
	 * Funcao para testes de carga usando o JMeter
	 * 
	 * @throws IOException
	 */
	@Deprecated
	@SuppressWarnings("unused")
	private void testeJMeter() throws IOException {

		String requisicao = new String();
		String resposta = new String();

		InputStream in = clientSocket.getInputStream();
		byte[] streamArray = new byte[1000];
		int n = in.read(streamArray);
		byte[] streamRequisicao = new byte[n];

		System.arraycopy(streamArray, 0, streamRequisicao, 0, n);

		requisicao = new String(streamRequisicao);

		run();

		DataOutputStream saidaParaCliente = new DataOutputStream(
				clientSocket.getOutputStream());

		saidaParaCliente.writeBytes(resposta);
	}

}
